﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Runtime.Serialization;
using System.Xml;
using FinPlusAssembler;
using FinPlusInterfaces;

namespace FinPlusBaseCore
{
    [DataContract]
    public class Bean : BeanBase, IBean
    {
        [DataMember]
        public override string TypeName { get { return Type.FullName; } set { Type = IOC.FindType(value); } }
        [DataMember]
        public override string Value { get { return _value.ToString(); } set { Parse(value, TimeStamp); } }
        public override object Object { get { return _value; } set { Update(value, TimeStamp); } }
        public string Display { get { return this.Display(); } set { this.SetDisplay(value); } }
        public event PropertyChangedEventHandler PropertyChanged;
        
        private static DataTable _compute;
        private IDictionary<string, IBean> _links;
        private IList<string> _expression;
        private IList<IBean> _totals;
        private bool _isDirty = true;
        private object _value;

        //construct
        public Bean() { }

        public Bean(int index, int id, XmlElement xmlBean, IDictionary<string, string> replace) { this.FromXml(index, id, xmlBean, replace); }

        public Bean(int index, int id, string name, object value, DateTime timeStamp, string user) { Initialise(index, id, name, value, timeStamp, user ?? Environment.UserName); }

        public Bean(BeanBase bean)
        {
            Index = bean.Index;
            Id = bean.Id;
            Name = bean.Name;
            Type = bean.Object.GetType();
            User = bean.User;
            _value = bean.Object;
        }

        private Bean(int index, int id, string name, string value, Type type, DateTime timeStamp, string user)
        {
            Index = index;
            Id = id;
            Name = name;
            Type = type;
            object obj = Type.ChangeType(value);
            Update(obj, user);
        }

        //public
        public void Initialise(int index, int id, string name, object value, DateTime timeStamp, string user = "Unknown")
        {
            Index = index;
            Id = id;
            Name = name;
            Type = value.GetType();
            Update(value, timeStamp, user);
        }
        
        public IBean Clone(int id) 
        {
            return new Bean(Index, id, Name, ToString(), Type, TimeStamp, User);
        }

        public IBean Clone(int index, int id, string name, string value, Type type, DateTime timeStamp, string user = "Unknown") 
        {
            return new Bean(index, id, name, value, type, timeStamp, user);
        }

        public void Refresh()
        {
            if (!_isDirty) 
                return;

            _isDirty = false;

            NotifyPropertyChanged(BeanField.Value.ToString());
        }

        public void Update(object obj, string user = null, bool notify = true) { Update(obj, DateTime.Now, user = null, notify = true); }

        public void Update(object obj, DateTime timeStamp, string user = null, bool notify = true)
        {
            try
            {
                if (user != null)
                    User = user;
                TimeStamp = timeStamp;

                if (Type.Equals(typeof(Pod)) &&  obj.GetType().Equals(typeof(string)))
                    _value = new Pod((object[,])obj.ToString().ToArray2d());
                else
                    _value = obj;

                NotifyPropertyChanged(BeanField.Value.ToString());
            }
            catch(Exception e)
            {
                throw new PodException(string.Format("Failed to set {0}", obj.ToString()),e);
            }
        }

        public void Evaluate(IPod pod, string expression)
        {
            _expression = expression.Split(new char[] { ']', '[' }, StringSplitOptions.RemoveEmptyEntries).ToList();
            _links = pod.Where(f => _expression.Contains(f.Name)).ToDictionary(i => i.Name, i => i);

            foreach (var bean in _links.Values)
                bean.PropertyChanged += Link_Update;

            if (_compute == null)
                _compute = new DataTable();

            Evaluate();
        }

        public void Total(IList<IBean> beans)
        {
            ClearLinks();

            _totals = beans;
            foreach (var total in _totals)
                total.PropertyChanged += Total_Update;

            Total();
        }

        public bool Equals(IBean bean)
        {
            if(!Index.Equals(bean.Index))
                return false;
           
            if(!this.Id.Equals(bean.Id))
               return false;

            if (!Name.Equals(bean.Name))
                return false;

            if(!Object.GetType().Equals(Object.GetType()))
                return false;

            if (!Type.Equals(bean.Type))
                return false;

            if (!ToString().Equals(bean.ToString()))
                return false;

            if (!User.Equals(bean.User))
                return false;

            if (!TypeName.Equals(TypeName))
                return false;

            return true;
        }

        public void FromXml(int index, int id, XmlElement xmlBean, IDictionary<string, string> replace)
        {
            Index = index;
            Id = id;

            DateTime datetime;
            DateTime.TryParse(xmlBean.Parse(BeanField.TimeStamp.ToString(), "0/0/0"), out datetime);

            var name = xmlBean.Attributes[BeanField.Name.ToString()].Value;
            var type = IOC.FindType(xmlBean.Parse(BeanField.Type.ToString(), "System.String"));
            var user = xmlBean.Parse(BeanField.User.ToString(), "Unknown");
            var value = xmlBean.Attributes[BeanField.Value.ToString()].Value.ReplaceClean(@"\t|\n|\r").Replace(replace);
            var obj = type.ChangeType(value);
            TimeStamp = datetime;
            Initialise(index, id, name, obj, datetime, user);
        }

        public XmlNode ToXml(XmlDocument xmlDoc)
        {
            var xmlBeanNode = xmlDoc.CreateNode(XmlNodeType.Element, Common.Bean.ToString(), null);

            xmlBeanNode.Attributes.Append(xmlDoc.Attribute(BeanField.Name.ToString(), Name));
            xmlBeanNode.Attributes.Append(xmlDoc.Attribute(BeanField.Value.ToString(), ToString()));
            xmlBeanNode.Attributes.Append(xmlDoc.Attribute(BeanField.Type.ToString(), Type.FullName));
            xmlBeanNode.Attributes.Append(xmlDoc.Attribute(BeanField.TimeStamp.ToString(), !TimeStamp.Equals(DateTime.MinValue) ? TimeStamp.ToString() : DateTime.Now.ToString()));
            if (User != null && !User.Equals("Unknown"))
                xmlBeanNode.Attributes.Append(xmlDoc.Attribute(BeanField.User.ToString(), User));

            return xmlBeanNode;
        }

        public void Parse(string value)
        {
            Parse(value, TimeStamp);
        }

        public override string ToString()
        {
            return _value.ToString();
        }

        //private
        private void Parse(string value, DateTime timeStamp, string user = null, bool notify = true)
        {
            try
            {
                if (user != null)
                    User = user;

                TimeStamp = timeStamp;

                if (Type.Equals(typeof(Pod)))
                    _value = new Pod((object[,])value.ToString().ToArray2d());
                else
                    _value = Type.ChangeType(value.ToString());

                NotifyPropertyChanged(BeanField.Value.ToString());
            }
            catch(Exception e)
            {
                throw new PodException(string.Format("Failed to set {0}", value.ToString()), e);
            }
        }

        private void Evaluate()
        {
            try
            {
                var val = double.NaN;
                var evaluate = string.Join(" ", _expression.Select(i => _links.ContainsKey(i) ? _links[i].ToString() : i).ToArray());
                var res = _compute.Compute(evaluate, null);
                double.TryParse(res.ToString(), out val);

                Update(val);
            }
            catch
            {
                Update(double.NaN);
            }
        }

        private void Total()
        {
            try
            {
                var total = 0.0;
                foreach (var i in _totals)
                    total += (double)i.Object;
                Update(total);
            }
            catch
            {
                Update(double.NaN);
            }
        }

        private void ClearLinks()
        {
            if (_links != null)
                foreach (var bean in _links.Values)
                    bean.PropertyChanged -= Link_Update;
            _links = null;

            Update( double.NaN);
        }

        private void NotifyPropertyChanged(string propName, bool notify = true)
        {
            if (PropertyChanged != null && notify)
                 PropertyChanged(this, new PropertyChangedEventArgs(propName));
            else if (PropertyChanged != null)
                _isDirty = true;
        }

        //event
        private void Link_Update(object s, PropertyChangedEventArgs a) { if (_links != null) Evaluate(); }
        private void Total_Update(object s, PropertyChangedEventArgs a) { if (_totals != null) Total(); }
    }
}





































































































//Copyright2013<jnrton>AllRightsReserved 
//Contact j.nrton@finpus